home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Shareware Grab Bag
/
Shareware Grab Bag.iso
/
090
/
byteibm.arc
/
PM_AT.ASM
< prev
next >
Wrap
Assembly Source File
|
1985-07-12
|
34KB
|
1,366 lines
NAME PM_AT
PAGE 60, 132
.286C
;
; PM/AT - A program to place the PC/AT into Protected Mode
; Copyright 1985, Ross P. Nelson
;
INCLUDE \usr\include\protect.inc
; Data structure definitions
DESCRIP STRUC ; generic descriptor format
limit DW ? ; offset if gate
phys_addr_lo DW ? ; selector if gate
phys_addr_hi DB ? ; wc if gate
access DB ? ; access rights
DW 0 ; reserved for 386
DESCRIP ENDS
TSS_BLOCK STRUC ; format of a TSS
back_link DW ? ; previously active TSS
rSP0 DW ? ; level 0 stack
rSS0 DW ?
rSP1 DW ? ; level 1 stack
rSS1 DW ?
rSP2 DW ? ; level 2 stack
rSS2 DW ?
rIP DW ?
FLAGS DW ?
rAX DW ?
rCX DW ?
rDX DW ?
rBX DW ?
rSP DW ?
rBP DW ?
rSI DW ?
rDI DW ?
rES DW ?
rCS DW ?
rSS DW ? ; active stack segment
rDS DW ?
task_LDT DW ? ; LDT selector
TSS_BLOCK ENDS
; Literal values for descriptor types
TSS EQU 1
LDT EQU 2
TSS_BUSY EQU 3
CALL_GATE EQU 4
TASK_GATE EQU 5
INT_GATE EQU 6
TRAP_GATE EQU 7
RDONLY EQU 0 ; read only
RD_WR EQU 1 ; read/write
RD_WR_XD EQU 3 ; read/write expand down
EXONLY EQU 4 ; execute only
EX_RD EQU 5 ; execute/readable
EXONLY_CF EQU 6 ; execute only/conforming
EX_RD_CF EQU 7 ; execute/readable/conforming
TSS_LIMIT EQU 43
; Segment building macros
MSEG MACRO name,type,priv,combine ;; start a memory segment
name SEGMENT PARA combine ;; MASM directive
zero = $ ;; for ALIGN macro
&name&_start = $ ;; origin
&name&_ar = 90h OR (priv SHL 5) OR (type SHL 1) ;; access rights
ENDM
SSEG MACRO name,type,priv ;; start a system segment
name SEGMENT PARA ;; MASM directive
zero = $ ;; for ALIGN macro
&name&_start = $ ;; origin
&name&_ar = 80h OR (priv SHL 5) OR type ;; access rights
ENDM
ENDSEG MACRO name ;; terminate a segment
&name&_limit = $ - &name&_start - 1 ;; create variable for seg limit
name ENDS ;; limit <- size-1 (0-FFFFh)
ENDM
; Descriptor building macros
DSCRP MACRO export,name ;; build descrip for segment
IFDIF <export>,<> ;; check for export name
export LABEL WORD
ENDIF
DW &name&_limit ;; segment limit
DW name ;; 16-bit segment addr
DB 0 ;; high order addr
DB &name&_ar ;; access rights
DW 0 ;; reserved
ENDM
GATE MACRO export,offset,select,wc,type,priv ;; build descriptor
IFDIF <export>,<> ;; check for export name
export LABEL WORD
ENDIF
DW offset ;; offset
DW select ;; segment selector
DB wc ;; word count
DB 80h OR (priv SHL 5) + type ;; access rights
DW 0 ;; reserved
ENDM
; Selector creating macros for Task segments
GDT_SEL MACRO sel,priv
DW sel + priv ;; assume sel = index * 8
ENDM
LDT_SEL MACRO sel,priv
DW sel + 4 + priv ;; like GDT but TI bit set
ENDM
; Utility macros
CALL_EX MACRO sel,rpl ;; call exported item
DB 9Ah ;; FAR call
DW 0 ;; no offset
DW sel + rpl ;; selector with req. priv.
ENDM
ALIGN MACRO bound ;; align $ on power of 2 bounds
LOCAL diff
diff = (($ - zero) AND (bound - 1)) ;; distance from bound
IF diff NE 0 ;; if on bound skip
ORG $ + (bound - diff) ;; else adjust
ENDIF
ENDM
PAGE
; This segment contains the Global Descriptor Table
MSEG GDT,RD_WR,0
; Required by INT 15
DESCRIP <0,0,0,0> ; GDT(0) always blank
DSCRP int15_gdt_dat,GDT ; DATA -> GDT
DSCRP int15_idt_dat,IDT ; DATA -> IDT
DSCRP ,DSC ; DATA -> DS
DSCRP ,DSC ; DATA -> ES
DSCRP ,DSC ; STACK -> SS
DSCRP ,INIT ; CODE -> CS
DESCRIP <0,0,0,0> ; CODE -> BIOS/int 15 reserved
DSCRP setup_tss,INIT_TSS ; TSS -> initial task
; Mini BIOS
DSCRP bio_dat,MBDAT ; DATA -> mini bios
DSCRP bios_seg,BIOS ; CODE -> mini bios
DSCRP disp_mono,MONO_RAM ; DATA -> monochrome display
DSCRP disp_color,COLOR_RAM ; DATA -> color display
; Fault handlers
DSCRP task_df,FTASK8 ; TSS -> double fault
xtra8 DESCRIP <ftask8_limit,FTASK8,0,92h> ; writable DATA alias for TSS
DSCRP task_tf,FTASK10 ; TSS -> task fault
xtra10 DESCRIP <ftask10_limit,FTASK10,0,92h> ; writable DATA alias for TSS
DSCRP task_sf,FTASK12 ; TSS -> task fault
xtra12 DESCRIP <ftask12_limit,FTASK12,0,92h> ; writable DATA alias for TSS
DSCRP fault_dat,FDAT ; DATA -> handler
DSCRP fhandler,HAND ; CODE -> handler
DSCRP falias,FDAT ; free for fault handler use
; Shared library
DSCRP share_lib,SHLIB ; CODE -> shared
GATE share_gate,shlib_start,share_lib,0,CALL_GATE,3 ; GATE to code
; Second task
DSCRP task2_tss,TASK2 ; TSS for 2nd task
DSCRP task2_ldt,T2LDT ; LDT for 2nd task
; Future use
DESCRIP <0,0,0,0> ; available
DESCRIP <0,0,0,0> ; available
DESCRIP <0,0,0,0> ; available
DESCRIP <0,0,0,0> ; available
ENDSEG GDT
PAGE
; This segment contains the Interrupt Descriptor Table.
MSEG IDT,RD_WR,0
; Chip level interrupts (0 - 1Fh)
GATE ,fault_00,fhandler,0,TRAP_GATE,0 ; DIVIDE
GATE ,fault_01,fhandler,0,TRAP_GATE,0 ; TRAP
GATE ,fault_02,fhandler,0,TRAP_GATE,0 ; NMI
GATE ,fault_03,fhandler,0,TRAP_GATE,0 ; BRKPT
GATE ,fault_04,fhandler,0,TRAP_GATE,0 ; INTO
GATE ,fault_05,fhandler,0,TRAP_GATE,0 ; BOUND
GATE ,fault_06,fhandler,0,TRAP_GATE,0 ; undef
GATE ,fault_07,fhandler,0,TRAP_GATE,0 ; 287 NAVAIL
GATE ,0,task_df,0,TASK_GATE,0 ; DBL FAULT
GATE ,fault_09,fhandler,0,TRAP_GATE,0 ; 287 OVRRUN
GATE ,0,task_tf,0,TASK_GATE,0 ; TSS FAULT
GATE ,fault_11,fhandler,0,TRAP_GATE,0 ; NP FAULT
GATE ,0,task_sf,0,TASK_GATE,0 ; STACK FAULT
GATE ,fault_13,fhandler,0,TRAP_GATE,0 ; GP FAULT
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,fault_16,fhandler,0,TRAP_GATE,0 ; 287 ERROR
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
GATE ,unknown,fhandler,0,TRAP_GATE,0
; System interrupts
; Hardware Level 0 (20-27) DOS equivalent vector
GATE ,timer_int,bios_seg,0,INT_GATE,0 ; 8
GATE ,kb_int,bios_seg,0,INT_GATE,0 ; 9
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; A
GATE ,com1_int,bios_seg,0,INT_GATE,0 ; B
GATE ,com2_int,bios_seg,0,INT_GATE,0 ; C
GATE ,prn2_int,bios_seg,0,INT_GATE,0 ; D
GATE ,fd_int,bios_seg,0,INT_GATE,0 ; E
GATE ,prn1_int,bios_seg,0,INT_GATE,0 ; F
; Hardware Level 1 (28-2F)
GATE ,rtc_int,bios_seg,0,INT_GATE,0 ; 70
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 71
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 72
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 73
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 74
GATE ,n287_int,bios_seg,0,INT_GATE,0 ; 75
GATE ,hd_int,bios_seg,0,INT_GATE,0 ; 76
GATE ,rsrv_int,bios_seg,0,INT_GATE,0 ; 77
; Mini BIOS (30 - 31)
GATE ,int_30,bios_seg,0,TRAP_GATE,3
GATE ,sw_reset,bios_seg,0,TRAP_GATE,0
ENDSEG IDT
PAGE
; Mini BIOS
; This section contains the "miniBIOS," a collection of
; routines for hardware support, including the interrupt
; handlers, and user-callable display routines.
; PC/AT Hardware Control
MSEG MONO_RAM,RD_WR,0,<AT 0B000h>
ORG 4000 ; end of monochrome RAM
ENDSEG MONO_RAM
MSEG COLOR_RAM,RD_WR,0,<AT 0B800h>
ORG 16 * 1024 ; end of color RAM
ENDSEG COLOR_RAM
MASTER EQU 20h ; master 8259A
SLAVE EQU 0A0h ; slave 8259A
DEV_COLOR EQU 3D4h ; color port
RETRACE_PORT EQU 3DAh ; port for horiz/vert retrace
DEV_MONO EQU 3B4h ; monochrome port
DEV_RTC EQU 70h ; real-time-clock port
EOI EQU 20h ; end of interrupt command
WR_DEVICE MACRO device,unit,data ; write to rtc or crt devices
IFDIF <device>,<>
mov dx, device
ENDIF
mov al, unit
out dx, al
inc dx
mov al, data
out dx, al
dec dx
ENDM
MSEG MBDAT,RD_WR,0
tick_ctr DD 0 ; incremented by timer int
kb_ctr DW -2 ; keyboard interrupts
; NOTE: This program is setup to run on a monochrome system only
; This pointer must be modified to support a color display.
display_ptr LABEL DWORD ; points to display RAM
DW 0 ; offset
DW disp_mono ; selector (MONOCHROME)
cursor LABEL word
cursor_x DB 0 ; column
cursor_y DB 0 ; row
attrib DB 7
ENDSEG MBDAT
MSEG BIOS,EXONLY,0
ASSUME CS:BIOS, DS:NOTHING
; INTERRUPT HANDLERS
; This is where the MINIBIOS comes when it gets a hardware interrupt.
; In this implementation, the only interrupt which is handled is
; the timer tick. The keyboard interrupt is also used as a signal
; to exit protected mode. The other handlers are left as an
; exercise for the user.
; Level 0 interrupts
timer_int:
push ax
push ds
mov ax, OFFSET bio_dat ; data seg selector
mov ds, ax
ASSUME DS:MBDAT
inc WORD PTR tick_ctr ; bump counter
adc WORD PTR tick_ctr[2], 0
mov al, EOI ; signal 8259A
out MASTER, al
pop ds
ASSUME DS:NOTHING
pop ax
iret
kb_int:
push ax
mov al, EOI
out MASTER, al
pop ax
int 31h ; RESET system
iret
com1_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret
com2_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret
prn2_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret
fd_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret
prn1_int:
push ax
mov al, EOI
out MASTER, al
pop ax
iret
; Level 1 interrupts - must EOI both the SLAVE and MASTER 8259As
rtc_int:
push ax
mov al, EOI
out SLAVE, al
out MASTER, al
pop ax
iret
n287_int:
push ax
mov al, EOI
out SLAVE, al
out MASTER, al
pop ax
iret
hd_int:
push ax
mov al, EOI
out SLAVE, al
out MASTER, al
pop ax
iret
rsrv_int:
int 1Fh ; cause failure
PAGE
; MiniBIOS user callable function codes
MBIOS_WR_CHAR EQU 0
MBIOS_WR_STRING EQU 1
MBIOS_WR_CRSR EQU 2
MBIOS_WR_ATTR EQU 3
MBIOS_BELL EQU 4
MBIOS_CLS EQU 5
; USER CALLABLE FUNCTIONS
; INT 30h
; Write to display -- All registers but AX preserved
; FN: AH = 0 Write character
; Input: AL = char
;
; FN: AH = 1 Write ASCIIZ string
; Input DS:SI -> string
;
; FN: AH = 2 Set cursor
; Input: DH = row
; DL = column
;
; FN: AH = 3 Set attribute
; Input: AL = attribute
;
; FN: AH = 4 Bell
;
; FN AH = 5 Clear Screen
;
int_30:
cld
or ah, ah ; determine function
jz co
dec ah
jnz $ + 5
jmp linout
dec ah
jnz $ + 5
jmp set_cursor
dec ah
jnz $ + 5
jmp set_attrib
dec ah
jnz $ + 5
jmp bell
dec ah
jnz $ + 5
jmp cls
iret
wr_cursor PROC NEAR
; Write HW cursor - cursor in DX, trashes AX, CX, DX
mov ax, 80 ; convert to 16 bit
mul dh
xor dh, dh
add dx, ax
mov cx, dx
WR_DEVICE DEV_MONO,14,ch ; write hardware
WR_DEVICE ,15,cl
ret
wr_cursor ENDP
co:
push cx ; save state
push di
push ds
push es
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
les di, display_ptr
mov ch, al ; save character
mov ax, 80 * 2 ; number of columns/row
mov cl, cursor_y ; time #rows
mul cl
add di, ax ; update offset
xor ax, ax ; zero
mov al, cursor_x ; column
shl al, 1 ; * 2
add di, ax ; update offset
mov al, ch ; restore character
mov ah, attrib ; get data
stosw
inc cursor_x ; ajust cursor position
cmp cursor_x, 80
jb co_done
sub cursor_x, 80
inc cursor_y
co_done: push dx
mov dx, cursor
call wr_cursor
pop dx
pop es
pop ds
ASSUME DS:NOTHING
pop di
pop cx
iret
linout:
push es ; save state
push si
push di
push cx
push ds
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
les di, display_ptr ; get screen pointer
mov ax, 80 * 2 ; number of columns/row
mov cl, cursor_y ; time #rows
mul cl
add di, ax ; update offset
xor ax, ax ; zero
mov al, cursor_x ; column
shl al, 1 ; * 2
add di, ax ; update offset
mov ah, attrib ; screen attribute
pop ds ; user data
ASSUME DS:NOTHING
xor cx, cx ; count
cld
linout_loop: lodsb
or al, al ; end of string?
jz line_done ; yes - quit loop
stosw ; no - write char/attrib
inc cx
jmp linout_loop ; write next char
line_done: push ds
mov ax, OFFSET bio_dat ; bios data segment
mov ds, ax
ASSUME DS:MBDAT
mov ax, cx ; count
mov cl, 80
div cl ; al = rows, ah = columns
add cursor_x, ah
cmp cursor_x, 80 ; overflow?
jb update_row ; no
sub cursor_x, 80 ; else adjust
inc al
update_row: add cursor_y, al
push dx
mov dx, cursor
call wr_cursor
pop dx
pop ds
ASSUME DS:NOTHING
pop cx
pop di ; start of chars written
pop si ; restore state
pop es
iret ; and return
set_cursor:
push cx
push dx
push ds
mov ax, OFFSET bio_dat ; bios data segment
mov ds, ax
ASSUME DS:MBDAT
mov cursor, dx ; save new cursor
call wr_cursor
pop ds
ASSUME DS:NOTHING
pop dx
pop cx
iret
set_attrib:
push cx
push ds
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
mov attrib, al
pop ds
ASSUME DS:NOTHING
pop cx
iret
bell:
push ax
push bx
push cx
mov bx, 200
in al, 61h ; get current state
push ax ; save it
bell_loop: and al, 0FCh ; speaker off
out 61h, al
mov cx, 60
idle1: loop idle1
or al, 002h ; speaker on
out 61h, al
mov cx, 180 ; duty cycle 1:3
idle2: loop idle2
dec bx ; test major loop
jnz bell_loop
pop ax
out 61h, al ; restore state
pop cx
pop bx
pop ax
iret
cls:
push cx ; save state
push dx
push di
push ds
push es
mov cx, OFFSET bio_dat ; bios data segment
mov ds, cx
ASSUME DS:MBDAT
les di, display_ptr
mov ah, attrib
mov al, ' '
mov cx, 80 * 25
cld
rep stosw
xor dx, cx
mov cursor, dx
call wr_cursor
pop es
pop ds
pop di
pop dx
pop cx
iret
;
; INT 31
; Reset processor
;
sw_reset PROC FAR
WR_DEVICE DEV_RTC,0Fh,0 ; write SHUTDOWN code to RTC
mov al, 0FEh ; HW SHUTDOWN
out 64h, al ; HW STATUS
halt: hlt
jmp halt
sw_reset ENDP
ENDSEG BIOS
PAGE
; FAULT HANDLERS
; In this prototype system, all the fault handler does is to display
; the name and location of the fault on the screen for a short period
; of time before resetting the system. This should provide the user
; with enough information to correct the problem.
; TSS for #DF - double fault handler
; #DF must have its own task to prevent shutdown
SSEG FTASK8,TSS,0
DW 0 ; back link
DW 0, 0 ; SS0:SP - unneeded/CPL=0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW fault_ts ; IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW fhandler_stack ; SP
DW fhandler_stack ; BP
DW msg_08, 0 ; SI/DI
GDT_SEL xtra8,0 ; ES
GDT_SEL fhandler,0 ; CS
GDT_SEL fault_dat,0 ; SS
GDT_SEL fault_dat,0 ; DS
DW 0 ; LDT selector
ENDSEG FTASK8
; TSS for #TF - task fault handler
; #TF must have its own task to ensure a valid machine state
SSEG FTASK10,TSS,0
DW 0 ; back link
DW 0, 0 ; SS0:SP - unneeded/CPL=0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW fault_ts ; IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW fhandler_stack ; SP
DW fhandler_stack ; BP
DW msg_10, 0 ; SI/DI
GDT_SEL xtra10,0 ; ES
GDT_SEL fhandler,0 ; CS
GDT_SEL fault_dat,0 ; SS
GDT_SEL fault_dat,0 ; DS
DW 0 ; LDT selector
ENDSEG FTASK10
; TSS for #SF - stack fault handler
; #SF requires its own task to prevent #DF in certain occasions
SSEG FTASK12,TSS,0
DW 0 ; back link
DW 0, 0 ; SS0:SP - unneeded/CPL=0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW fault_ts ; IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW fhandler_stack ; SP
DW fhandler_stack ; BP
DW msg_12, 0 ; SI/DI
GDT_SEL xtra12,0 ; ES
GDT_SEL fhandler,0 ; CS
GDT_SEL fault_dat,0 ; SS
GDT_SEL fault_dat,0 ; DS
DW 0 ; LDT selector
ENDSEG FTASK12
MSEG FDAT,RD_WR,0
; Data for fault handlers
msg_00 DB "*** DIVIDE FAULT ***", 0
msg_01 DB "*** SINGLE STEP TRAP ***", 0
msg_02 DB "*** NMI ***", 0
msg_03 DB "*** INT 3 ***", 0
msg_04 DB "*** OVERFLOW EXCEPTION ***", 0
msg_05 DB "*** BOUND EXCEPTION ***", 0
msg_06 DB "*** UNDEFINED OPCODE ***", 0
msg_07 DB "*** 287 NOT AVAILABLE ***", 0
msg_08 DB "*** DOUBLE FAULT ***", 0
msg_09 DB "*** 287 SEGMENT OVERRUN ***", 0
msg_10 DB "*** ILLEGAL TSS FAULT ***", 0
msg_11 DB "*** NOT PRESENT FAULT ***", 0
msg_12 DB "*** STACK FAULT ***", 0
msg_13 DB "*** GENERAL PROTECTION FAULT ***", 0
msg_16 DB "*** 287 EXCEPTION ***", 0
msg_fcode DB "*** Fault code = ",0
msg_faddr DB "*** Fault address = ",0
msg_unknown DB "*** UNKNOWN EXCEPTION ***", 0
msg_buffer DB 40 DUP (0)
ALIGN 2 ; force stack to word boundary
DW 64 DUP (0)
fhandler_stack LABEL WORD
ENDSEG FDAT
MSEG HAND,EXONLY,0
; Code for fault handlers
ASSUME CS:HAND, DS:FDAT
fault_00: mov si, OFFSET msg_00
jmp fail
fault_01: mov si, OFFSET msg_01
jmp fail
fault_02: mov si, OFFSET msg_02
jmp fail
fault_03: mov si, OFFSET msg_03
jmp fail
fault_04: mov si, OFFSET msg_04
jmp fail
fault_05: mov si, OFFSET msg_05
jmp fail
fault_06: mov si, OFFSET msg_06
jmp fail
fault_07: mov si, OFFSET msg_07
jmp fail
fault_08: mov si, OFFSET msg_08
jmp fail
fault_09: mov si, OFFSET msg_09
jmp fail
fault_10: mov si, OFFSET msg_10
jmp fail
fault_11: mov si, OFFSET msg_11
jmp fail
fault_12: mov si, OFFSET msg_12
jmp fail
fault_13: mov si, OFFSET msg_13
jmp fail
fault_16: mov si, OFFSET msg_16
jmp fail
unknown: mov si, OFFSET msg_unknown
jmp fail
; All fault handlers that have a task switch come here
fault_ts:
pop ax ; error code
mov bx, ES:[back_link] ; selector of faulting task
lar dx, bx ; check if accessable
jnz fake_data ; invalid
test dh, 80h ; check present bit
jz fake_data ; invalid
and dh, 1Fh ; mask - leaving type info
cmp dh, TSS_BUSY ; should point to user TSS
jne fake_data ; invalid
lsl dx, bx ; get segment size
cmp dx, TSS_LIMIT ; ensure size OK
jb fake_data ; branch too small
; At this point, we know that the back link points to a
; valid TSS, we now wish to create a readable data segment
; that points to the same physical location as the TSS so
; we can extract some information from it. Since this segment
; is created pointing to the same address as a previously
; existing segment, it is called an ALIAS
mov di, OFFSET falias ; offset of free descriptor
mov dx, OFFSET int15_gdt_dat; selector for GDT as
mov es, dx ; if it were a data seg
and bx, 0FFF8h ; convert selector to offset
mov cx, ES:[bx].phys_addr_lo; get phys addr of user TSS
mov ES:[di].phys_addr_lo, cx; store in free descriptor
mov cl, ES:[bx].phys_addr_hi; continue with high byte
mov ES:[di].phys_addr_hi, cl
mov ES:[di].limit, TSS_LIMIT; complete free descriptor
mov es, di ; use as selector to segment
push ES:[rCS] ; push task's CS
push ES:[rIP] ; push task's IP
push ax ; error code
jmp fail
fake_data: push WORD PTR 0FFFFh ; can't get real info
push WORD PTR 0FFFEh ; push false CS, IP
push ax ; error code
jmp fail
pause PROC NEAR
mov bx, 10
ploop: mov cx, 0FFFFh
loop $
dec bx
jnz ploop
ret
pause ENDP
fail: mov ax, OFFSET fault_dat ; get legal DS
mov ds, ax
mov es, ax
mov dx, 0 ; cursor x=0/y=0
mov ah, MBIOS_WR_CRSR ; home cursor
int 30h
mov ah, MBIOS_WR_STRING ; write msg
int 30h
mov dx, 0100h ; cursor x=0/y=1
mov ah, MBIOS_WR_CRSR ; home cursor
int 30h
; check if error code on stack
cmp si, OFFSET msg_08 ; was DF fault?
je show_code
cmp si, OFFSET msg_10 ; was TF fault?
je show_code
cmp si, OFFSET msg_11 ; was NP fault?
je show_code
cmp si, OFFSET msg_12 ; was SF fault?
je show_code
cmp si, OFFSET msg_13 ; was GP fault?
jne show_addr
show_code: mov si, OFFSET msg_fcode ; print code message
mov ah, MBIOS_WR_STRING
int 30h
pop dx ; get code from stack
mov di, OFFSET msg_buffer
mov ah, LIB_BIN_HEX ; convert to hex
CALL_EX share_gate,0
mov si, OFFSET msg_buffer ; and print
mov ah, MBIOS_WR_STRING
int 30h
mov dx, 0200h ; cursor x=0/y=2
mov ah, MBIOS_WR_CRSR ; home cursor
int 30h
show_addr:
mov si, OFFSET msg_faddr ; print addr message
mov ah, MBIOS_WR_STRING
int 30h
pop bx ; get offset
pop dx ; get segment
push bx ; save offset
mov di, OFFSET msg_buffer
mov ah, LIB_BIN_HEX
CALL_EX share_gate,0
mov si, OFFSET msg_buffer
mov ah, MBIOS_WR_STRING
int 30h
mov al, ':'
mov ah, MBIOS_WR_CHAR
int 30h
pop dx ; offset
mov di, OFFSET msg_buffer
mov ah, LIB_BIN_HEX
CALL_EX share_gate,0
mov si, OFFSET msg_buffer
mov ah, MBIOS_WR_STRING
int 30h
call pause ; wait
call pause
call pause
mov ah, MBIOS_BELL ; bell
int 30h
call pause
mov ah, MBIOS_BELL ; bell
int 30h
call pause
mov ah, MBIOS_BELL ; bell
int 30h
call pause ; wait
call pause
call pause
call pause
int 31h ; reset processor
ENDSEG HAND
PAGE
MSEG SHLIB,EX_RD_CF,0
;
; This segment implements a library of shared functions that may be
; invoked through gate "share_gate". The segment is conforming, so its
; code will run at the same privelege as the caller. The calling
; sequence is merely to set up the registers and CALL the gate. If an
; illegal function number is called, the system issues a DIVIDE BY 0.
; Only registers BP, SP, CS, DS, ES, and SS are guaranteed preserved.
;
ASSUME CS:SHLIB, DS:NOTHING, ES:NOTHING
LIB_SINT_BIN EQU 0
LIB_UINT_BIN EQU 1
LIB_HEX_BIN EQU 2
LIB_BIN_SINT EQU 3
LIB_BIN_UINT EQU 4
LIB_BIN_HEX EQU 5
shlib_code PROC FAR
cld ; set direction for string fns
cmp ah, 5 ; beyond last function?
jbe index ; no - do indexing
xor ax, ax ; zero ax
div al ; force divide fault
index: mov bl, ah ; get FN code
xor bh, bh ; clear high order
shl bx, 1 ; convert FN to index
add bx, OFFSET table
jmp WORD PTR CS:[bx] ; invoke function.
table DW sint_bin
DW uint_bin
DW hex_bin
DW bin_sint
DW bin_uint
DW bin_hex
; Function 0 / ASCII SIGNED INT to BINARY conversion
; AH = 0
; DS:SI -> Null terminated string of digits
; Returns:
; AX <- 16-bit signed integer
; CY <- set if error
;
sint_bin:
mov cx, 10 ; multiply constant
xor ax, ax ; initialize accumulator
xor dx, dx
xor bh, bh ; sign flag FALSE
cmp BYTE PTR [si], '-' ; signed?
jne get_schar ; no
inc si ; next char
inc bh ; set signed flag
get_schar: mov bl, [si] ; get input character
inc si ; bump ptr
or bl, bl ; end of string?
jz set_sign
cmp bl, '0' ; check valid
jb err_ret
cmp bl, '9'
ja err_ret
sub bl, '0' ; convert digit to binary
mul cx ; decimal shift left
add al, bl ; new digit
adc ah, 0 ; propogate carry
js err_ret ; quit if sign overflow
adc dx, 0
jnz err_ret ; quit if overflow
jmp get_schar
set_sign: or bh, bh ; sign flag on
jz done ; no - return
neg ax ; else complement
done: clc ; no error
ret
err_ret: stc ; CY is error flag
ret
; Function 1 / ASCII UNSIGNED INT to BINARY conversion
; AH = 1
; DS:SI -> Null terminated string of digits
; Returns:
; AX <- 16-bit unsigned integer
; CY <- set if error
;
uint_bin:
mov cx, 10 ; multiply constant
xor ax, ax ; initialize accumulator
xor dx, dx
get_uchar: mov bl, [si] ; get input character
inc si ; bump ptr
or bl, bl ; end of string?
jz done ; yes - return
cmp bl, '0' ; check valid
jb err_ret
cmp bl, '9'
ja err_ret
sub bl, '0' ; convert digit to binary
mul cx ; decimal shift left
add al, bl ; new digit
adc ah, 0 ; propogate carry
adc dx, 0
jnz err_ret ; quit if overflow
jmp get_uchar
; Function 2 / ASCII HEX to BINARY conversion
; AH = 2
; DS:SI -> Null terminated string of digits
; Returns:
; AX <- 16-bit unsigned
; CY <- set if error
;
hex_bin:
xor dx, dx ; init accumulator
get_hchar: lodsb ; get character
or al, al ; last char?
jnz test_hchars
mov ax, dx
ret ; CY cleared by OR
test_hchars: cmp al, '0' ; check valid digit
jb err_ret
cmp al, '9'
jbe got_valid
or al, 20h ; must be alpha - force lower
cmp al, 'a' ; check valid char
jb err_ret
cmp al, 'f'
ja err_ret
sub al, 27h ; adjust range
got_valid: sub al, '0' ; convert digit to binary
cmp dx, 0FFFh ; test overflow
ja err_ret
shl dx, 4 ; hex shift left
add dl, al ; insert new digit
jmp get_hchar
; Function 3 / BINARY to ASCII SIGNED INT conversion
; AH = 3
; DX -> 16-bit signed
; ES:DI -> Buffer for ascii string
; Returns:
; Null terminated ASCII string at ES:DI
;
bin_sint: test dh, 80h ; sign bit?
jz bin_uint ; no - treat as unsigned
mov al, '-' ; else write sign
stosb
neg dx ; and complement
jmp bin_uint
div_tab DW 10000
DW 1000
DW 100
DW 10
DW 1
; Function 4 / BINARY to ASCII UNSIGNED INT conversion
; AH = 4
; DX -> 16-bit unsigned
; ES:DI -> Buffer for ascii string
; Returns:
; Null terminated ASCII string at ES:DI
;
bin_uint: mov si, OFFSET div_tab ; index
xor bx, bx ; bh is zero suppress flag
mov ax, dx ; value
u_loop: cmp WORD PTR CS:[si], 1 ; last divisor?
je u_out ; yes - output last digit
xor dx, dx ; high order zero
div WORD PTR CS:[si] ; DX:AX/10^n
or ax, bx ; quotient == 0 || ! suppress?
jz u_loop
mov bh, 1 ; turn off zero suppress flag
add al, '0' ; quotient always single digit
stosb
mov ax, dx ; restore AX with remainder
inc si
inc si ; next divisor
jmp u_loop
u_out: add al, '0' ; last digit
stosb
xor al, al ; ASCII null
stosb
ret
; Function 5 / BINARY to ASCII HEX conversion
; AH = 5
; DX -> 16-bit unsigned
; ES:DI -> Buffer for ascii
; Returns:
; Null terminated 4 character ASCII string at ES:DI
;
bin_hex: mov al, dh ; high order byte
shr al, 4 ; high nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h1
add al, 7 ; ajust alpha
bin_h1: stosb
mov al, dh ; high order byte
and al, 0Fh ; low nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h2
add al, 7 ; ajust alpha
bin_h2: stosb
mov al, dl ; low order byte
shr al, 4 ; high nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h3
add al, 7 ; ajust alpha
bin_h3: stosb
mov al, dl ; low order byte
and al, 0Fh ; low nybble
add al, '0' ; convert to ASCII
cmp al, '9' ; test value > '9'
jbe bin_h4
add al, 7 ; ajust alpha
bin_h4: stosb
xor al, al
stosb ; ASCII null
ret
shlib_code ENDP
ENDSEG SHLIB
PAGE
;
; This section contains the main code and data. We come here initially
; in Real Address Mode, perform necessary setup, and enter Protected
; Virtual Address Mode. The data segment has combine type STACK
; so that the linker will initialize SS:SP.
;
MSEG DSC,RD_WR,0,STACK
no_pm_msg DB '*** Unable to enter protected mode ***$'
blank_line DB 80 DUP (' ')
DB 0
msg DB 'Testing',0
ALIGN 2 ; force stack to word bound
DW 100 DUP (?) ; stack
ENDSEG DSC
SSEG INIT_TSS,TSS,0
TSS_BLOCK <> ; uninitialized
ENDSEG INIT_TSS
MSEG INIT,EXONLY,0
ASSUME CS:INIT, DS:DSC
adjust_addr PROC NEAR
; This subroutine marches through a descriptor table to fixup 16-bit
; segment addresses to full 24-bit physical addresses. Since the
; segment fixups were done by the DOS linker in Real Address Mode,
; all we need to do is multiply by 16. We assume the high order 8
; bits are zero, i.e., all addresses are in the first 1Mb.
; Called with ES:0 pointing to table, CX is number of entries.
xor bx, bx ; initial offset
l1: mov al, ES:[bx].access ; get access rights byte
test al, 10h ; is descriptor a segment?
jnz got_seg ; yes
and al, 0Fh ; extract type
cmp al, 3 ; gate?
ja update_next ; yes - skip segment adjust
got_seg: mov ax, ES:[bx].phys_addr_lo; get segment
mov dx, 16
mul dx ; convert to phys addr
mov ES:[bx].phys_addr_lo, ax; store
mov ES:[bx].phys_addr_hi, dl; 24 bits
update_next: add bx, 8 ; incr to next descrip
loop l1
ret
adjust_addr ENDP
start:
mov ax, DSC ; set up DS
mov ds, ax
sti
; When DOS created the prototype descriptors, it placed segment addresses
; in the physical address portion of the segment descriptors. We must
; fix up all descirptor tables which contain segment descriptors.
mov ax, T2LDT
mov es, ax ; point to proto LDT
mov cx, t2ldt_limit ; get limit
inc cx ; bump to size in bytes
shr cx, 3 ; convert to # entries
call adjust_addr
mov ax, GDT
mov es, ax ; point to proto GDT
mov cx, gdt_limit
inc cx
shr cx, 3 ; gdt entries
call adjust_addr
; Now we ask the BIOS to place us in protected mode. The BIOS requires
; the first 7 descriptors of the GDT to be setup as we have done. This
; gives it enough information to load GDTR and IDTR and setup a new
; code and data segment for the calling routine. The BIOS will also
; program the 8259A to our requested interrupt vectors. Additionally, it
; sets up the internal AT hardware to allow addresses > 1Mb to go out
; over the bus (frees A20 line).
xor si, si ; ES:SI -> proto GDT
mov bh, 20h ; int level 1 start
mov bl, 28h ; int level 2 start
mov ah, 89h ; enter PM request
mov cx, 0FFFFh ; idle here to ensure all
loop $ ; DOS keybd ints processed
int 15h ; BIOS call
jnc vm ; successful if no CY bit
mov ah, 9 ; no - print message
mov dx, OFFSET no_pm_msg
int 21h
mov ax, 4C01h ; failure
int 21h ; exit
;;; NOW IN PROCTED MODE -- INTS DISABLED
vm:
mov bp, sp ; setup registers
mov ax, ds
mov es, ax
mov ax, OFFSET setup_tss ; active task
ltr ax
pm_init_done:
mov ah, MBIOS_BELL ; bell
int 30h
call idle
mov ah, MBIOS_CLS ; cls
int 30h
; Enable ints
xor al, al ; no ints masked
out MASTER+1, al
out SLAVE+1, al
sti
; Print number of ticks so far
print_ticks:
mov ah, MBIOS_CLS ; clear screen
int 30h
mov dx, 0010h
mov ah, MBIOS_WR_CRSR
int 30h
mov ax, OFFSET bio_dat
mov es, ax
cli
mov dx, WORD PTR ES:tick_ctr ; get tick counter
mov ax, WORD PTR ES:tick_ctr[2]
sti
push dx
call pr_hex_word ; print high order
pop ax
call pr_hex_word ; print low order
call idle ; pause
CALL_EX task2_tss,0 ; invoke task 2
call idle
call idle
mov ah, MBIOS_BELL ; bell
int 30h
jmp print_ticks ; loop forever
idle PROC NEAR
push bx
push cx
mov bx, 10
iloop: mov cx, 0FFFFh
loop $
dec bx
jnz iloop
pop cx
pop bx
ret
idle ENDP
pr_hex_word PROC
; Print word in AX
push bp
mov bp, sp
sub sp, 10 ; space for string on stack
push ds
pop es ; es = ds
lea di, [bp-10] ; destination
mov dx, ax ; value
mov ah, LIB_BIN_HEX ; function
CALL_EX share_gate,0 ; shared code
lea si, [bp-10] ; hex string ptr
mov ah, MBIOS_WR_STRING ; function
int 30h ; print string
mov sp, bp
pop bp
ret
pr_hex_word ENDP
ENDSEG INIT
PAGE
; Finally, we have a small second task, which will alternate execution
; with the initial task. It runs at privelege level 3, which means it
; has access only to its code segment, data segment, the shared library
; gate and INT 30h.
SSEG T2LDT,LDT,0
; All memory segments for this task reside in a local descriptor table.
DSCRP task2_cs,CODE2 ; CS for 2nd task
DSCRP task2_dsc,DSC2 ; DS/SS for 2nd task
DSCRP task2_stk0,STK2_0 ; Level 0 stack for OS calls
ENDSEG T2LDT
SSEG TASK2,TSS,0
DW 0 ; back link
DW STK2_0_limit + 1 ; SP0
LDT_SEL task2_stk0,0 ; SS0
DW 0, 0 ; SS1:SP
DW 0, 0 ; SS2:SP
DW top ; initial IP
DW 0 ; flags
DW 4 DUP (0) ; AX/CX/DX/BX
DW stack2 ; SP
DW stack2 ; BP
DW 2 DUP (0) ; SI/DI
LDT_SEL task2_dsc,3 ; ES - segments all in LDT
LDT_SEL task2_cs,3 ; CS
LDT_SEL task2_dsc,3 ; SS
LDT_SEL task2_dsc,3 ; DS
GDT_SEL task2_ldt,0 ; LDT selector
ENDSEG TASK2
MSEG STK2_0,RD_WR,0
DW 128 DUP (?) ; stack for level 0 execution
ENDSEG STK2_0
MSEG DSC2,RD_WR,3
; Data and stack segment for 2nd task
t2_msg DB "Task 2 running",0
ALIGN 2 ; stack on word boundary
DW 128 DUP (?)
stack2 LABEL WORD
ENDSEG DSC2
MSEG CODE2,EXONLY,3
ASSUME CS:CODE2, DS:DSC2
top: ; task starts here first time
mov dx, 0032h
mov ah, MBIOS_WR_CRSR
int 30h ; cursor to "safe" location
mov si, OFFSET t2_msg
mov ah, MBIOS_WR_STRING
int 30h ; print message
iret ; return to previous task
jmp top ; when task invoked again,
; CS:IP points here (after IRET)
ENDSEG CODE2
END start